iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0

來到了第12天,今日來講解第二個軟體設計常用實作-代理模式,也是解決重複出現程式碼的方案之一,話不多說來了解設計模式之一的代理模式

代理模式簡介

字面上的意思可以猜到目的是在「代替執行」,透過代理的方式操作實際實例,可以防止外部訪問到實際實例,由於在外部間隔一層代理類別,因此可以在實際物件調用方法的前後,添加其他執行的邏輯操作,代理模式的組成包含了下列三項

代理器(Proxy):具體代理實例,負責調用實際對象的實作方法

  • 代理主題(Subject):實際是個介面,定義要代理的抽象方法
  • 實際對象(Real Object):實際要被代理的對象,實作了代理主題的抽象方向

實際執行流程,先決定好代理主題要抽象的方法,在讓實際對象進行實作代理實作介面,最後在由代理器負責呼叫實際對象,實際實作如下

// 代理主題
interface Subject {
    public void handler();
}

// 實作邏輯的被代理物件
class RealProxyA implements Subject {
    public void handler() {
        System.out.println("RealProxy運行業務邏輯");
    }
}

// 代理器
class class Proxy {
    public void run(Subject instance) {
        System.out.println("準備代理實例運行方法");
        // 執行主要的執行方法
        instance.handler();
        System.out.println("代理方法結束");
    }
}

// Main.java
public static void main(String[] args){
    Subject subjectA = new Subject();
    Proxy proxy = new Proxy();
    // 透過代理執行 run方法
    proxy.run(subjectA);
}

這段程式碼運用代理器呼叫具體物件的方法,並在方法執行前後,添加其他業務邏輯,在詳細閱讀程式執行流程,可以得知代理器調用的代理方法將參數抽象化,在內部可以透過代理主題的抽象實作,實現核心邏輯的抽換,並在方法運行前後,添加了其他實作方法

代理模式的缺點

雖然代理模式能加強複用性,並減少重複的程式碼,但設想需要代理的行為一旦變多,就需要製造多個代理模式核心元件,也要呼叫多個代理器且「複刻」相似結構的類別,這個做法違背了代理模式誕生的初衷,為了解決這個問題,衍伸出了動態代理的實作方式

代理模式改良版-動態代理

動態代理實現方式,利用Java提供的reflectAPI內的InvocationHandler、Method和Proxy三種元件,實現真實類別方法調用,現在先來個簡單的動態代理

開頭先引用實現代理的反射物件

java.lang.reflect.InvocationHandler;
java.lang.reflect.Method;
java.lang.reflect.Proxy;
定義抽象代理主題

interface Subject {
    public String handler();
}

建立真實代理物件類別

class Cat implements Subject {
        @Override
        public String handler() {
            // System.out.println("貓咪叫");
            String action = "學貓叫";
            System.out.printf("布偶貓使用 瘋狂的%s\n", action);
            return action;
        }
}

建立動態代理執行器,負責真實代理物件的邏輯封裝

class ProxtHandler implements InvocationHandler {
        // 實際被代理的物件,必須實作代理主題介面
        private Object realInstance;

        /**
         * @param instance 實際要代理的物件
         */
        public ProxtHandler(Object instance) {
            this.realInstance = instance;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 調用實際被代理的物件方法,並取得回傳內容
            Object invokeResult = method.invoke(realInstance, args);
            return invokeResult;
        }

        // 前處理
        private void before() {
            System.out.println("Before run handler");
        }

        // 後處理
        private void after() {
            System.out.println("After run handler");
        }

    }

最後在 Main方法實現相關邏輯

public static void main(String[] args) {
    // 將被代理物件放照代理器裡面
    Cat crazyRagdoll = new Cat();
    ProxtHandler proxyHandler = new ProxtHandler(crazyRagdoll);
    // Proxy工廠取得代理器實例
    Subject proxyInstance = (Subject) Proxy.newProxyInstance(
            // 載入要代理的類別參考
            Subject.class.getClassLoader(),
            // 要參考的類別
            new Class<?>[] { Subject.class },
            // 放 InvocationHandler 實作的實例
            proxyHandler);
    // 執行代理主題要執行的方法
    Object result = proxyInstance.handler();
}

接著執行程式碼,成功打印「布偶貓,瘋狂的喵喵叫」字樣,現在將 before跟after方法放入 invoke方法內,改寫後的程式碼

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    // 方法調用前的邏輯
    this.before();

    // 調用實際被代理的物件方法,並取得回傳內容
    Object invokeResult = method.invoke(realInstance, args);

    // 方法調用後的邏輯
    this.after();

    return invokeResult;
}

實際運行程式,成功在原本的文字前後,添加了兩個方法內打印的字樣,看到這可能有人有疑問,動態代理與原本的靜態代理相似,只是多了反射類別提供的原件,將代理邏輯進行封裝,事實不以為然,仔細回頭看程式碼

Proxy.newProxyInstance API建立代理者的參數中,參考了多個 interface的規範,讓實際代理者能執行多個接口的方法,在不創建新的代理者的狀況下,這個做法能統一代理方法的調用,反觀靜態代理每次建立新的代理主題時,就要建立相應的代理器,來實現代理模式

參考文章

菜豬-Java設計模式
動態代理大揭密


上一篇
第十一日 從荷包蛋看依賴性注入
下一篇
第十三日 Java動態載入與IOC元件
系列文
掌握Java神器,駕馭SpringBoot猛獸30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言